/////////////////////////////////////////////////////////////////////////////
/*
  Copyright 2002 Ronald S. Burkey.

  This file is part of GutenMark.

  GutenMark is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  GutenMark is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with GutenMark; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  Filename:	which.c 
  Purpose:	Attempts to determine the pathname of the executable
  		file, given argv[0] as a parameter.  What we are looking
		for is either an absolute pathname or a pathname relative
		to the current directory, and we don't much care which
		one it is.
  Mods:		07/21/02 RSB	Created.
  		07/22/02 RSB 	Now add .exe in WIN32 if it isn't already
				present.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

#ifdef WIN32
#define Sep1 '\\'
#define Sep2 ':'
#define PathSep ';'
#else
#define Sep1 '/'
#define Sep2 0
#define PathSep ':'
#endif

static char *PathnameBuffer = NULL;
static int PathnameSpace = 0;
static char *argv0 = NULL;
static int argv0Space = 0;

//--------------------------------------------------------------------------
// Returns a pointer to the pathname, or NULL on failure.

char *
RelativeWhich (const char *RawArgv0)
{
  FILE *fp;
  char *s, *PathEnvVar, *Path, SepFound;
  int i, j, Argv0Size;

  if (RawArgv0 == NULL)
    return (NULL);
    
  // We replace the input string with a buffered version of it.  The 
  // reason has to do with the potential necessity of adding ".exe" to it
  // in Win32.  
  Argv0Size = strlen (RawArgv0) + 5;
  if (Argv0Size > argv0Space)
    {
      argv0 = realloc (argv0, Argv0Size);
      if (argv0 == NULL)
        {
	  argv0Space = 0;
          return (NULL);
	}
      argv0Space = Argv0Size;	
    }  
  strcpy (argv0, RawArgv0);  
  Argv0Size = strlen (argv0) + 1;
    
#ifdef WIN32    
  // Turns out that *some* versions of Win32 (like Win 98) automatically add
  // the ".exe" extension to argv[0].  Other versions (like Win 2K) *don't*.
  // So, we have to check whether it's present and add it if not.  Because of
  // the way the argv0 buffer is allocated, we're guaranteed that there's 
  // enough room.
  if (strcasecmp (&argv0[Argv0Size - 5], ".EXE"))
    {
      strcat (argv0, ".EXE");  
      Argv0Size += 4;
    }
#endif // WIN32  

  // First thing:  The only case in which argv[0] itself isn't already
  // good enough, as given, is if the executable was located via the 
  // PATH.  If argv[0] already contains any of the usual 
  // path/drive separators, then it's fine as-is.
  for (s = (char *) argv0; *s; s++)
    if (*s == Sep1 || *s == Sep2)
      return ((char *) argv0);

#ifdef WIN32
  // In MS-DOS, the current directory is always tried first, and only
  // then is the PATH used.  So, we have to check if the file is in the
  // the current directory. Fortunately, argv[0] does actually contain 
  // the entire filename of the executable (i.e., the extension ".exe"
  // has already been added to it), so we don't need to worry about that.
  fp = fopen (argv0, "r");
  if (fp != NULL)
    {
      fclose (fp);
      return (argv0);
    }
#endif // WIN32

  // Sadly, the executable apparently *was* located via the PATH.
  // We have no choice but to parse the PATH ourselves, and simply look
  // in all of the directories to see if the executable was there or
  // not. 
  PathEnvVar = getenv ("PATH");
#ifdef WIN32
  if (PathEnvVar == NULL)
    return (NULL);
#else
  if (PathEnvVar == NULL)
    PathEnvVar = ".";
#endif
  // Parse the PATH environment variable for each path string.
  for (Path = PathEnvVar; *Path;)
    {
      // Look for ';' or ':', whichever is appropriate for the platform.
      for (s = Path + 1; *s && *s != PathSep; s++);
      SepFound = *s;
      *s = 0;
      // Now figure out the size of the pathname.
      i = s - Path;
      j = i;
      if (s[-1] != Sep1 && s[-1] != Sep2)
	i++;
      i += Argv0Size;
      // Reallocate space for the pathname, if necessary.  This 
      // transparently does the initial allocation also.
      if (i > PathnameSpace)
	{
	  PathnameSpace = i;
	  PathnameBuffer = realloc (PathnameBuffer, i);
	  if (PathnameBuffer == NULL)
	    {
	      PathnameSpace = 0;
	      return (NULL);
	    }
	}
      // Construct the pathname.
      strcpy (PathnameBuffer, Path);
      if (s[-1] != Sep1 && s[-1] != Sep2)
	PathnameBuffer[j++] = Sep1;
      strcpy (PathnameBuffer + j, argv0);
      // Now check to see if it exists!
      fp = fopen (PathnameBuffer, "r");
#ifdef WIN32
      if (fp != NULL)
	{
	  fclose (fp);
	  return (PathnameBuffer);
	}
#else // WIN32
      if (fp != NULL)
	{
	  struct stat StatBuf;
	  // In the case of *nix, we can't automaticaly conclude that
	  // the file is correct, though we've found one of the proper 
	  // name, because it may not actually be an *executable*.
	  // So let's check that out.  I'm too darned lazy to figure 
	  // out the permissions fully, so I'll assume that if *any* 
	  // of the executable flag bits is set then it's ok.
	  i = fstat (fileno (fp), &StatBuf);
	  fclose (fp);
	  if (i == 0
	      && 0 != (StatBuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
	    return (PathnameBuffer);
	}
#endif // WIN32
      // Sadly, the file did not exist.  Let's move on to the next
      // directory in the PATH environment variable.
      if (SepFound)
	s++;
      Path = s;
    }

  // Well, never found it -- too bad!
  return (NULL);
}
